package com.kwok.hotel.es.db.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.kwok.hotel.es.db.entity.Hotel;
import com.kwok.hotel.es.db.entity.bo.HotelBo;
import com.kwok.hotel.es.db.entity.vo.SearchParam;
import com.kwok.hotel.es.db.service.EsSearchService;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.common.geo.GeoDistance;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MultiMatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.IndexOperations;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.stream.Collectors;

@Service
@Slf4j
public class EsSearchServiceImpl implements EsSearchService {

    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;

    /**
     * 创建索引库
     */
    @Override
    public void createIndex() {
        IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(HotelBo.class);
        //判断索引库是否存在，如果存在则结束该创建索引库方法
        if (indexOperations.exists()) return;
        //创建索引库
        indexOperations.create();
        //创建映射类型
        indexOperations.putMapping(HotelBo.class);
        log.debug("创建索引库和映射类型成功");
    }

    /**
     * 保存酒店数据信息到索引库
     * @param hotel
     * @return
     */
    @Override
    public Integer saveHotel(Hotel hotel) {

        //首先进行对象转换，将Hotel实体对象转为HotelBo业务对象,其中HotelBo中所有Hotel实体对象相同的属性会自动映射转换
        HotelBo hotelBo = BeanUtil.toBean(hotel, HotelBo.class);
        log.debug("Bean的转换：{}", hotelBo);
        //手动处理两个对象中不同的属性经纬度
        hotelBo.setLocation(hotel.getLon(),hotel.getLat());
        //保存数据到ES索引库
        HotelBo save = elasticsearchRestTemplate.save(hotelBo);
        return 1;
    }

    @Override
    public List<Hotel> search(SearchParam searchParam) {

        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        //关键字查询
        if (StringUtils.hasLength(searchParam.getKeyword())){
            MultiMatchQueryBuilder multiMatchQueryBuilder = QueryBuilders.multiMatchQuery(searchParam.getKeyword())
                    .field("subject",10)
                    .field("tags",8)
                    .field("subject.pinyin");
            boolQueryBuilder.must(multiMatchQueryBuilder);
        }

        //星级查询
        if (searchParam.getStar() != null){
            boolQueryBuilder.must(QueryBuilders.rangeQuery("star").gte(searchParam.getStar()));
        }

        //价格范围查询
        if (searchParam.getMinPrice() == null)
            searchParam.setMinPrice(0D);
        if (searchParam.getMaxPrice() == null)
            searchParam.setMaxPrice(Double.MAX_VALUE);
        boolQueryBuilder.must(QueryBuilders.rangeQuery("price")
                .gte(searchParam.getMinPrice())
                .lte(searchParam.getMaxPrice()));

        //经纬度坐标查询
        if (searchParam.getLon() != null && searchParam.getLat() != null){
            boolQueryBuilder.must(QueryBuilders.geoDistanceQuery("hotelLocation")
                    .point(searchParam.getLon(), searchParam.getLat())
                    .distance(10, DistanceUnit.KILOMETERS)
                    .geoDistance(GeoDistance.ARC));
        }

        //构建查询对象
        Query query = new NativeSearchQuery(boolQueryBuilder);

        //执行查询
        SearchHits<HotelBo> hotelBoSearchHits = elasticsearchRestTemplate.search(query, HotelBo.class);

        //转换
        return hotelBoSearchHits.getSearchHits().stream().map(hotelBoSearchHit -> {
            HotelBo hotelBo = hotelBoSearchHit.getContent();
            //将HotelBo对象装换成Hotel对象
            Hotel hotel = BeanUtil.toBean(hotelBo, Hotel.class);
            //手动转换经纬度坐标
            hotel.setLon(hotelBo.getHotelLocation().getLon());
            hotel.setLat(hotelBo.getHotelLocation().getLat());
            return hotel;
        }).collect(Collectors.toList());

    }

}
